package org.box.demos.multithreading.wgetmt.core;

import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Provide several methods that work with obtained web-content such as looking
 * up of all available links for the recursive mode, converting links to
 * point on local (already saved or will be saved soon) files.
 * 
 * @author <a href="mailto:alexander.box@gmail.com">Alexander Korobka</a>
 * 
 */
public class ContentParser {

	private final static Pattern SRC_PATTERN_DAUBLE_QUOTES = Pattern
			.compile("(href|src)\\s*=\\s*\"([^\"]+)\"");

	private final static Pattern SRC_PATTERN_SINGLE_QUOTES = Pattern
			.compile("(href|src)\\s*=\\s*\'([^\']+)\'");

	private static final int URL_GROUP_NUMBER = 2;

	/**
	 * Constructs parser with unique urls' map
	 * 
	 * @param uniqueUrlsMap
	 *            - a map with unique urls
	 */
	public ContentParser() {
	}

	/**
	 * Returns a map of urls/content types from the given content
	 * 
	 * @param content
	 *            - the content of the retrieved web-page
	 * @param url
	 *            - the url of the page's content
	 * @param dp 
	 *			  - the pattern of urls to download 
	 * @param sp -the pattern of urls to skip
	 * 				
	 * 
	 * @return a map of urls/content types
	 */
	public Set<URL> findUrls(String content, URL url, Pattern dp, Pattern sp) {

		Set<URL> set = findUrls(url, SRC_PATTERN_DAUBLE_QUOTES.matcher(content), dp, sp);
		set.addAll(findUrls(url, SRC_PATTERN_SINGLE_QUOTES.matcher(content), dp, sp));

		return set;
	}

	/**
	 * Returns a map of urls/content types from the given content
	 * 
	 * @param url
	 *            - the url of the page's content
	 * @param matcher
	 *            - RegEx matcher for matching urls in the content
	 * @param dp 
	 *			  - the pattern of urls to download 
	 * 
	 * @return a map of urls/content types
	 */
	public Set<URL> findUrls(URL url, Matcher matcher, Pattern dp, Pattern sp) {
		Set<URL> set = new LinkedHashSet<URL>(1000);
		while (matcher.find()) {
			String urlStr = matcher.group(URL_GROUP_NUMBER).trim();
			if (urlStr.startsWith("#") || urlStr.startsWith("javascript:") || urlStr.startsWith("mailto:")) {
				continue;
			}
			// remove inner path, if it's present
			urlStr = URL.removeInnerPath(urlStr);
			
			URL u = normalizeUrl(urlStr, url);
			if ((dp == null ||  dp.matcher(u.toString()).find()) 
					&& (sp == null || !sp.matcher(u.toString()).find())) {
				
				set.add(u);
			}
		}
		return set;
	}

	/**
	 * Converts all available urls to point on local (already saved or will be
	 * saved soon) files
	 * 
	 * @param url
	 *            - a url of web-page
	 * @param content
	 *            - a content of the retrieved web-page
	 * @param converter
	 *            - a urls' converter
	 * 
	 * @return a new content with converted urls
	 * 
	 * @throws IOException
	 *             if an inner url has incorrect format.
	 */
	public String convertUrls(URL url, String content, Converter converter, Storage storage)
			throws IOException {

		content = convertUrls(url, content, converter,
				SRC_PATTERN_DAUBLE_QUOTES.matcher(content), storage);
		content = convertUrls(url, content, converter,
				SRC_PATTERN_SINGLE_QUOTES.matcher(content), storage);
		return content;
	}

	/**
	 * 
	 * Converts all available urls to point on local (already saved or will be
	 * saved soon) files
	 * 
	 * @param url
	 *            - a url of web-page
	 * @param content
	 *            - a content of the retrieved web-page
	 * @param converter
	 *            - a urls' converter
	 * @param matcher
	 *            - RegEx matcher for matching urls in the content
	 * 
	 * @return the content with converted urls
	 * @throws IOException 
	 */
	public String convertUrls(URL url, String content, Converter converter,
			Matcher matcher, Storage storage) throws IOException {
		List<Record> replaceList = new LinkedList<Record>();
		while (matcher.find()) {
			String urlStr = matcher.group(URL_GROUP_NUMBER).trim();
			if (urlStr.startsWith("#") || urlStr.startsWith("javascript:") || urlStr.startsWith("mailto:")) {
				continue;
			}

			URL u = normalizeUrl(urlStr, url);
			if (storage.containsUrl(u)) {
				replaceList.add(new Record(u, matcher.start(URL_GROUP_NUMBER),
						matcher.end(URL_GROUP_NUMBER)));
			}
		}

		int start = 0;
		StringBuilder sb = new StringBuilder();
		for (Record record : replaceList) {

			String newPath = converter.convert(record.getUrl(), url);

			sb.append(content.substring(start, record.getStart()));
			sb.append(newPath);

			start = record.getEnd();
		}
		sb.append(content.substring(start));

		return sb.toString();
	}

	/**
	 * Converts the given url to the common form of urls and creates new a URL
	 * object instance.
	 * 
	 * @param url
	 *            - the url to normalizing
	 * @param pageUrl
	 *            - a url of a page where the given <code>url<code> is located.
	 * 
	 * @return returns a new instance of URL object
	 */
	private URL normalizeUrl(String url, URL pageUrl) {
		// urls like: '../../index.html' or './index.html'
		if (url.startsWith(".")) {
			return new URL(pageUrl.getProtocol() + pageUrl.getPath(true) + url);
		}

		// urls like: '//google.com/search'
		if (url.startsWith("//")) {
			return new URL(pageUrl.getProtocol() + url.substring(2));
		}
		// urls like: '/search'
		if (url.startsWith("/")) {
			return new URL(pageUrl.getProtocol() + pageUrl.getHost() + url);
		}
		// urls like: 'http://google.com/search'
		if (url.contains("//")) {
			return new URL(url);
		} else {
			// urls like: 'search'
			return new URL(pageUrl.getProtocol() + pageUrl.getPath(true) + url);
		}
	}

	/**
	 * Represents a fragment of content's code that contains a url and uses for
	 * url's converting to a format of the file system.
	 * 
	 * It also has a start and end positions of the url in the content.
	 * 
	 * @author <a href="mailto:alexander.box@gmail.com">Alexander Korobka</a>
	 * 
	 */
	private class Record {

		private final URL url;
		private final int start;
		private final int end;

		public Record(URL url, int start, int end) {
			this.url = url;
			this.start = start;
			this.end = end;
		}

		public URL getUrl() {
			return url;
		}

		public int getStart() {
			return start;
		}

		public int getEnd() {
			return end;
		}

	}

}
